package tr.helper.controller.server;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import io.vavr.Tuple2;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import tr.helper.CommonUtil;
import tr.helper.SessionPoolManager;
import tr.helper.domain.DataConfig;
import tr.helper.domain.ServerOperatorCell;
import tr.helper.repository.dao.ServerDao;
import tr.helper.repository.table.Config;
import tr.helper.repository.table.ServerInfo;
import tr.helper.view.ServerInfoItem;

@Slf4j
public class ServerListController implements Initializable {

    public static SessionPoolManager sessionPoolManager = new SessionPoolManager();
    public static String data = "";
    @FXML
    private TableView<ServerInfoItem> serverTv;
    @FXML
    private TextField txtName;
    @FXML
    private TabPane tab;
    @FXML
    private TextArea txtLog;
    @FXML
    private Label labelDbPath;
    private Stage newStage;
    private Stage taskStage;

    protected ServerDao serverDao = new ServerDao();
    public Stage stageEdit = null;
    private static volatile double xValue = 0;
    public static final double incr = 5;
    private Timer timer = new Timer();
    private Timer clearTimer = new Timer();
    List<ServerInfoItem> serverList = Lists.newArrayList();
    private static final String DEPLOY_SHELL_NAME = "deploy.sh";
    private static final String BACKUP_NAME = "backup";
    // 临时目录(先上传要发布的包到该目录，然后停服务，做替换，再启动(尽最大可能降低服务不可用时间))
    private static final String TEMP_NAME = "server_deploy_temp";
    private static final Map<String, Image> imgCache = Maps.newHashMap();
    /**
     * 需要定时清理的目录
     */
    private static final String TEMP_DATA = "/home/truethink/data/";
    // 图表相关
    @FXML
    private LineChart<Number, Number> chart;
    private XYChart.Series<Number, Number> memorySeries;
    private XYChart.Series<Number, Number> cpuSeries;
    private static final Image loading = new Image(Config.class.getResourceAsStream("/1.gif"));
    private HBox loadingBox;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        labelDbPath.setText("dbPath=" + getClass().getResource("/" + DataConfig.DB).getPath());
        txtLog.setEditable(true);
        serverTv.getColumns().addAll(CommonUtil.getTableCols(ServerInfoItem.class, list -> {
            TableColumn<ServerInfoItem, Void> editColumn = new TableColumn<>("操作");
            editColumn.setPrefWidth(290);
            // 设置Edit列的单元格工厂
            editColumn.setCellFactory(column -> new ServerOperatorCell<>(tp2 -> doDeploy(tp2),
                tp2 -> doStop(tp2),
                tp2 -> doStart(tp2),
                tp2 -> doRollback(tp2),
                tp2 -> doTask(tp2)));
            list.add(editColumn);
        }));

        // 创建数据系列
        memorySeries = new XYChart.Series<>();
        memorySeries.setName("剩余内存%");
        cpuSeries = new XYChart.Series<>();
        cpuSeries.setName("CPU使用率%");

        // 将数据系列添加到图表中
        chart.getData().addAll(memorySeries, cpuSeries);

        // 定时更新Value列的值
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                for (ServerInfoItem item : serverList) {
                    final String key = item.getHost() + item.getPort();
                    Session session = sessionPoolManager.getSession(key, item.getHost(), item.getPort(), item.getUsername(), item.getPassword());
                    if (null == session) {
                        continue;
                    }
                    String imgName = getImgByStatus(item, session);
                    if (imgName != null) {
                        Image image = imgCache.get(imgName);
                        if (image == null) {
                            image = new Image(Config.class.getResourceAsStream("/" + imgName));
                            imgCache.put(imgName, image); // 将图像缓存起来
                        }
                        if (null != item.getStatus() && image.equals(item.getStatus().getImage())) {
                        } else {
                            ImageView imageView = new ImageView();
                            imageView.setFitHeight(32);
                            imageView.setFitWidth(32);
                            imageView.setImage(image);
                            item.setStat(imgName);
                            item.setStatus(imageView);
                        }
                    }
                    // 检测服务器用量
                    item.setUsed(getServerUsed(item, session));
                    sessionPoolManager.releaseSession(key, session);
                }
                var items = serverTv.getSelectionModel().getSelectedItems();
                ServerInfoItem item;
                if (null != items && (item = items.get(0)) != null) {
                    xValue += incr;
                    if (!StringUtils.equals(item.getHost(), chart.getTitle())) {
                        Platform.runLater(() -> chart.setTitle(item.getHost()));
                    }
                    final String key = item.getHost() + item.getPort();
                    Session session = sessionPoolManager.getSession(key, item.getHost(), item.getPort(), item.getUsername(), item.getPassword());
                    if (null != session) {
                        // 获取服务器数据
                        double freeMemory = getFreeMemory(session);
                        double totalMemory = getTotalMemory(session);
                        double cpuUsage = getCPUUsage(session);

                        // 更新图表数据
                        updateChart(freeMemory, totalMemory, cpuUsage);
                    }
                    sessionPoolManager.releaseSession(key, session);
                }
                Platform.runLater(() -> {
                    serverTv.refresh();
                });
            }
        }, 0, 5 * 1000); // 每1000毫秒更新一次
    }

    private double getFreeMemory(Session session) {
        // 执行获取剩余内存命令，解析命令输出，返回剩余内存值
        double freeMemory = 0.0;
        try {
            // 创建Channel
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand("free | grep Mem | awk '{print $4/$2 * 100.0}'");

            // 执行命令并获取输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
            channel.connect();

            // 解析命令输出
            String line;
            if ((line = reader.readLine()) != null) {
                freeMemory = Double.parseDouble(line);
            }

            // 关闭Channel
            channel.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return freeMemory;
    }

    private double getTotalMemory(Session session) {
        // 执行获取总内存命令，解析命令输出，返回总内存值
        double totalMemory = 0.0;
        try {
            // 创建Channel
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand("free | grep Mem | awk '{print $2}'");

            // 执行命令并获取输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
            channel.connect();

            // 解析命令输出
            String line;
            if ((line = reader.readLine()) != null) {
                totalMemory = Double.parseDouble(line);
            }

            // 关闭Channel
            channel.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return totalMemory;
    }

    private double getCPUUsage(Session session) {
        // 执行获取CPU使用率命令，解析命令输出，返回CPU使用率
        double cpuUsage = 0.0;
        try {
            // 创建Channel
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand("top -bn 2 -d 0.2 | grep '^%Cpu' | tail -1 | awk '{print $2 + $4}'");

            // 执行命令并获取输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
            channel.connect();

            // 解析命令输出
            String line;
            if ((line = reader.readLine()) != null) {
                cpuUsage = Double.parseDouble(line);
            }

            // 关闭Channel
            channel.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return cpuUsage;
    }

    private TranslateTransition chartTranslate = new TranslateTransition(Duration.millis(200), chart);

    private void updateChart(double freeMemory, double totalMemory, double cpuUsage) {
        Platform.runLater(() -> {
            // 添加数据点到数据系列
            var cpuData = cpuSeries.getData();
            var memoryData = memorySeries.getData();
            cpuData.add(new XYChart.Data<>(xValue, cpuUsage));
            memoryData.add(new XYChart.Data<>(xValue, freeMemory));

            // 手动设置 X 轴的 lowerBound
            int pointMax = 20;
            boolean isFull = cpuData.size() > pointMax;
            if (isFull) {
                cpuData.remove(0);
                memoryData.remove(0);
            }

            // 更新 X 轴范围
            NumberAxis x = (NumberAxis) chart.getXAxis();
            x.setLowerBound(cpuData.get(0).getXValue().doubleValue());
            x.setUpperBound(cpuData.get(cpuData.size() - 1).getXValue().doubleValue() + incr);

            chartTranslate.setToX(-incr);
            chartTranslate.playFromStart();
        });
    }

    private void doRollback(Tuple2<Button, ServerInfoItem> tp2) {
        // 创建新窗口
        if (null != newStage && newStage.isShowing()) {
            return;
        }
        newStage = new Stage();
        newStage.setTitle("版本回滚");

        // 设置要传递的参数
        ServerInfoItem server = tp2._2;
        newStage.setUserData(server);

        ComboBox<String> comboBox = new ComboBox<>();
        ObservableList<String> observableList = FXCollections.observableArrayList();
        // 读取服务器备份目录
        final String backupFolder = server.getServerAppRoot() + BACKUP_NAME;

        final String key = server.getHost() + server.getPort();
        Session session = sessionPoolManager.getSession(key, server.getHost(), server.getPort(), server.getUsername(), server.getPassword());
        try {
            ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            Vector<LsEntry> fileList = channelSftp.ls(backupFolder);
            channelSftp.disconnect();
            for (ChannelSftp.LsEntry entry : fileList) {
                if ("后端".equals(server.getType())) {
                    if (!entry.getAttrs().isDir()) {
                        final long fileSize = entry.getAttrs().getSize() / 1024;
                        observableList.add(entry.getFilename() + "#[" + fileSize + "kb]");
                    }
                } else {
                    if (entry.getAttrs().isDir() && !StringUtils.contains(entry.getFilename(), ".")) {
                        observableList.add(entry.getFilename());
                    }
                }
            }
        } catch (Exception e) {
            log.error("", e);
        }
        comboBox.setItems(observableList);
        comboBox.setPrefWidth(330);

        Button rollbackBtn = new Button("选定并回滚");
        rollbackBtn.setLayoutX(330);

        // 打印按钮的事件处理
        rollbackBtn.setOnAction(e -> {
            ServerInfoItem item = (ServerInfoItem) newStage.getUserData();
            String selectValue = comboBox.getSelectionModel().getSelectedItem();
            if (StringUtils.isBlank(selectValue)) {
                CommonUtil.alert(newStage, "请选择一个要回滚的版本!");
                return;
            }
            final String selectVersion = StringUtils.split(selectValue, "#")[0];
            // 创建一个Task，用于执行耗时操作
            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() {
                    Platform.runLater(() -> {
                        rollbackBtn.setDisable(true);
                        rollbackBtn.setText("回滚中...");
                    });
                    Tab tab2 = tab.getTabs().get(1);
                    // 设置Tab2为当前选中的Tab
                    tab2.getTabPane().getSelectionModel().select(tab2);

                    if ("后端".equals(item.getType())) {
                        // 先停服务
                        sendCmd(item, Arrays.asList("03"));
                        // 还原
                        final String backupJar = server.getServerAppRoot() + BACKUP_NAME + "/" + selectVersion;
                        final String targetJar = server.getServerAppRoot() + server.getTargetJar();
                        execute(session, String.format("cd %s; cp %s %s", server.getServerAppRoot(), backupJar, targetJar));
                        log("服务器:{} 拷贝备份文件 {} 还原到 {} 准备启动...", server.getName(), backupJar, targetJar);
                        // 启动
                        sendCmd(item, Arrays.asList("05"));
                    }
                    if ("前端".equals(item.getType())) {
                        // 还原
                        final String backupWeb = server.getServerAppRoot() + BACKUP_NAME + "/" + selectVersion
                            + "/" + server.getTargetJar();
                        final String targetWeb = server.getServerAppRoot();
                        final String curWebDir = targetWeb + "/" + server.getTargetJar();
                        final String rollCommand = String.format("cd %s; rm -rf %s; mkdir %s; cp -r %s %s", server.getServerAppRoot(), curWebDir, curWebDir, backupWeb, targetWeb);
                        execute(session, "exec", rollCommand, line -> log("服务器返回:{}", line), errLine -> log("服务器返回错误:{}", errLine));
                        log("服务器:{} 清空 {} 拷贝备份文件 {} 还原到 {}", server.getName(), curWebDir, backupWeb, targetWeb);
                    }
                    Platform.runLater(() -> {
                        newStage.close();
                    });
                    return null;
                }
            };
            // 启动任务
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        });

        // 创建HBox容器，并将按钮放入其中
        HBox hbox = new HBox(comboBox, rollbackBtn);

        // 创建VBox容器，并将HBox放入其中
        VBox vbox = new VBox(hbox);

        // 创建新窗口场景
        Scene newScene = new Scene(vbox, 420, 50);
        newStage.setScene(newScene);
        newStage.setResizable(false);
        // 显示新窗口
        newStage.show();
    }

    private void doStart(Tuple2<Button, ServerInfoItem> tp2) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("服务启动提示");
        alert.setContentText("确定要启动服务么?");
        Button button = tp2._1;

        if (alert.showAndWait().orElse(null) == javafx.scene.control.ButtonType.OK) {

            Tab tab2 = tab.getTabs().get(1);
            // 设置Tab2为当前选中的Tab
            tab2.getTabPane().getSelectionModel().select(tab2);

            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() {
                    Platform.runLater(() -> txtLog.clear());
                    sendCmd(tp2._2, Arrays.asList("01", "05"));
                    Platform.runLater(() -> button.setDisable(false));
                    return null;
                }
            };
            // 在后台线程中执行任务
            Thread thread = new Thread(task);
            thread.setDaemon(true); // 设置为守护线程，当主界面关闭时自动结束线程
            thread.start();
        }
    }

    private void doStop(Tuple2<Button, ServerInfoItem> tp2) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("服务停止警告");
        alert.setContentText("确定要停止服务么?");
        Button button = tp2._1;

        if (alert.showAndWait().orElse(null) == javafx.scene.control.ButtonType.OK) {

            Tab tab2 = tab.getTabs().get(1);
            // 设置Tab2为当前选中的Tab
            tab2.getTabPane().getSelectionModel().select(tab2);

            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() {
                    Platform.runLater(() -> txtLog.clear());
                    sendCmd(tp2._2, Arrays.asList("01", "03"));
                    Platform.runLater(() -> button.setDisable(false));
                    return null;
                }
            };
            // 在后台线程中执行任务
            Thread thread = new Thread(task);
            thread.setDaemon(true); // 设置为守护线程，当主界面关闭时自动结束线程
            thread.start();
        }
    }

    private void doDeploy(Tuple2<Button, ServerInfoItem> tp2) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("部署提示");
        alert.setHeaderText(null);
        alert.setContentText("确定要发布么?");

        Button button = tp2._1;
        // 显示确认弹窗，并根据用户的选择执行相应操作
        if (alert.showAndWait().orElse(null) == javafx.scene.control.ButtonType.OK) {
            // 执行编辑操作，例如弹出对话框来编辑数据
            button.setDisable(true);

            Tab tab2 = tab.getTabs().get(1);
            // 设置Tab2为当前选中的Tab
            tab2.getTabPane().getSelectionModel().select(tab2);

            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() {
                    Platform.runLater(() -> txtLog.clear());
                    sendCmd(tp2._2, Arrays.asList("00", "01", "02", "03", "04", "05"));
                    Platform.runLater(() -> button.setDisable(false));
                    return null;
                }
            };
            // 在后台线程中执行任务
            Thread thread = new Thread(task);
            thread.setDaemon(true); // 设置为守护线程，当主界面关闭时自动结束线程
            thread.start();
        }
    }

    private static ScheduledExecutorService executor = null;
    private static boolean isRunning = false;
    private Button startBtn, stopBtn;

    private void doTask(Tuple2<Button, ServerInfoItem> tp2) {
        // 创建新窗口
        if (null != taskStage && taskStage.isShowing()) {
            taskStage.toFront();
            return;
        }
        taskStage = new Stage();
        taskStage.setTitle("请选择任务");

        // 设置要传递的参数
        ServerInfoItem server = tp2._2;
        taskStage.setUserData(server);

        ComboBox<String> comboBox = new ComboBox<>();
        ObservableList<String> observableList = FXCollections.observableArrayList();

        final String key = server.getHost() + server.getPort();
        Session session = sessionPoolManager.getSession(key, server.getHost(), server.getPort(), server.getUsername(), server.getPassword());
        final Vector<LsEntry> fileList = new Vector<>();
        try {
            ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            fileList.addAll(channelSftp.ls(TEMP_DATA));
            if (!fileList.isEmpty()) {
                observableList.add("自动清理超3天的目录[" + TEMP_DATA + "]");
            }
            channelSftp.disconnect();
        } catch (Exception e) {
            log.error("", e);
        } finally {
            sessionPoolManager.releaseSession(key, session);
        }
        if (fileList.isEmpty()) {
            CommonUtil.alert(null, "该服务器未配置任何可执行的任务!");
        }
        comboBox.setItems(observableList);
        comboBox.setValue(observableList.get(0));
        comboBox.setPrefWidth(330);

        startBtn = new Button("启动");
        startBtn.setLayoutX(330);
        startBtn.setDisable(isRunning);
        startBtn.setOnAction(e -> {
            String selectValue = comboBox.getSelectionModel().getSelectedItem();
            if (StringUtils.contains(selectValue, "自动清理超3天的目录")) {
                txtLog.clear();
                startTimer(server, fileList);
            } else {
                CommonUtil.alert(taskStage, "请先选择一项任务");
            }
        });

        stopBtn = new Button("停止");
        stopBtn.setLayoutX(450);
        stopBtn.setDisable(!isRunning);
        stopBtn.setOnAction(e -> {
            pauseTimer();
        });

        // 创建HBox容器，并将按钮放入其中
        HBox hbox = new HBox(comboBox, startBtn, stopBtn);

        ImageView imageView = new ImageView();
        imageView.setFitHeight(35);
        imageView.setFitWidth(35);
        imageView.setImage(loading);
        loadingBox = new HBox(imageView);
        loadingBox.setVisible(isRunning);
        loadingBox.setAlignment(Pos.CENTER);

        // 创建VBox容器，并将HBox放入其中
        VBox vbox = new VBox(hbox, loadingBox);
        // 创建新窗口场景
        Scene newScene = new Scene(vbox, 420, 55);
        taskStage.setScene(newScene);
        taskStage.setResizable(false);
        // 显示新窗口
        taskStage.show();
    }

    private void startTimer(ServerInfoItem server, Vector<LsEntry> fileList) {
        isRunning = true;
        Tab tab2 = tab.getTabs().get(1);
        // 设置Tab2为当前选中的Tab
        tab2.getTabPane().getSelectionModel().select(tab2);
        Platform.runLater(() -> {
            startBtn.setDisable(isRunning);
            stopBtn.setDisable(!isRunning);
            loadingBox.setVisible(isRunning);
        });
        if (executor == null) {
            executor = Executors.newScheduledThreadPool(1);
            executor.scheduleAtFixedRate(() -> {
                if (!isRunning) {
                    return;
                }
                final String key = server.getHost() + server.getPort();
                Session session = sessionPoolManager.getSession(key, server.getHost(), server.getPort(), server.getUsername(), server.getPassword());
                for (ChannelSftp.LsEntry entry : fileList) {
                    if (entry.getAttrs().isDir()) {
                        if (Arrays.asList("..", ".").contains(entry.getFilename())) {
                            continue;
                        }
                        final String delDir = TEMP_DATA + entry.getFilename();
                        // 获取文件夹的最后更新时间
                        long lastModified = entry.getAttrs().getMTime() * 1000L;
                        // 转换为LocalDate
                        LocalDate localDate1 = new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                        LocalDate localDate2 = new Date(lastModified).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                        // 计算天数差异
                        long daysDiff = ChronoUnit.DAYS.between(localDate2, localDate1);
                        if (daysDiff > 3) {
                            try {
                                log("准备清理目录:{} 最后更新时间 {}", delDir, new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date(lastModified)));
                                execute(session, "exec", "rm -rf " + delDir, line -> log("服务器返回:{}", line), errLine -> log("服务器返回错误:{}", errLine));
                                log("清理目录:{} 成功，当前时间:{} 30分钟后将再次检测", delDir, new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date()));
                            } catch (Exception e) {
                                log("清理目录:{} 异常:{}", delDir, e.getMessage());
                            }
                        }
                    }
                }
                sessionPoolManager.releaseSession(key, session);
            }, 0, 30, TimeUnit.MINUTES);
        }
    }

    private void pauseTimer() {
        isRunning = false;
        Platform.runLater(() -> {
            startBtn.setDisable(isRunning);
            stopBtn.setDisable(!isRunning);
            loadingBox.setVisible(isRunning);
        });
    }

    private String getServerUsed(ServerInfoItem item, Session session) {
        StringBuilder serverInfo = new StringBuilder();
        execute(session, "exec", "top -bn1 | grep '%Cpu' | awk '{print $2}'", line -> {
            serverInfo.append("cpu使用率: ").append(line).append("\r\n");
        }, errLine -> log("服务端错误输出: " + errLine));

        execute(session, "exec", "free -h | grep Mem | awk '{print $4}'", line -> {
            serverInfo.append("空闲内存余量: ").append(line).append("\r\n");
        }, errLine -> log("服务端错误输出: " + errLine));

        execute(session, "exec", "free -h | grep Mem | awk '{print $7}'", line -> {
            serverInfo.append("可用内存余量: ").append(line).append("\r\n");
        }, errLine -> log("服务端错误输出: " + errLine));

        execute(session, "exec", "df -h --total | awk '/total/{print $4}'", line -> {
            serverInfo.append("磁盘剩余空间: ").append(line).append("\r\n");
        }, errLine -> log("服务端错误输出: " + errLine));

        return serverInfo.toString();
    }

    private String getImgByStatus(ServerInfoItem item, Session session) {
        if (!"后端".equals(item.getType())) {
            return null;
        }
        try {
            final String targetShell = String.format("%s%s", item.getServerAppRoot(), DEPLOY_SHELL_NAME);
            String statusCommand = String.format("sh %s status %s %s", targetShell, item.getAppPort(), item.getTargetJar());
            AtomicReference<String> status = new AtomicReference<>();
            execute(session, "exec", statusCommand, line -> {
                log.info("服务端输出: " + line);
                if (StringUtils.contains(line, DEPLOY_SHELL_NAME) && StringUtils.containsIgnoreCase(line, "no such")) {
                    String sourceShell = getResource(DEPLOY_SHELL_NAME);
                    log("服务器:{} 准备上传 {} 用于状态检测", item.getName(), sourceShell);
                    toUnix(sourceShell);
                    try {
                        ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
                        channelSftp.connect();
                        channelSftp.put(sourceShell, targetShell, progressMonitor, ChannelSftp.OVERWRITE);
                        channelSftp.disconnect();
                        log("服务器:{} 文件上传成功 用于状态检测", item.getName());
                    } catch (Exception e) {
                        log("服务器:{} 检测状态时传输shell {} 失败 {}", item.getName(), sourceShell, e.getMessage());
                    }
                }
                if (StringUtils.contains(line, "is running...")) {
                    status.set("1.gif");
                } else if (StringUtils.contains(line, "is running success")) {
                    status.set("2.gif");
                } else {
                    status.set("0.gif");
                }
            }, errLine -> log("服务端错误输出: " + errLine));
            return status.get();
        } catch (Exception e) {
            return null;
        }
    }

    @FXML
    public void select() {
        serverTv.getItems().clear();
        var cond = new ServerInfo();
        cond.setDeleted(0);
        if (StringUtils.isNotBlank(txtName.getText())) {
            cond.setName(txtName.getText());
        }
        serverList = serverDao.selectAll(cond).stream().map(a -> BeanUtil.copyProperties(a, ServerInfoItem.class)).collect(Collectors.toList());
        serverTv.getItems().addAll(serverList);
    }

    @FXML
    public void openAddForm() throws IOException {
        saveServerForm("");
    }

    private void saveServerForm(String id) throws IOException {
        if (null != stageEdit) {
            stageEdit.close();
            stageEdit = null;
        }
        data = id;
        AnchorPane pane = FXMLLoader.load(CommonUtil.getFxmlResource("server/ServerAddForm.fxml"));
        stageEdit = new Stage();
        stageEdit.setScene(new Scene(pane));
        stageEdit.setTitle("服务器信息");
        stageEdit.show();
    }

    @FXML
    public void editServer() throws IOException {
        var items = serverTv.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "必须选一行你想改的数据!");
            return;
        }
        saveServerForm(items.get(0).getId().toString());
    }

    @FXML
    public void delServer() {
        var items = serverTv.getSelectionModel().getSelectedItems();
        if (CollectionUtil.isEmpty(items)) {
            CommonUtil.alert(null, "你先选一个行么?");
            return;
        }
        if (CommonUtil.confirm(null, "操作确认", "确定要删除么?")) {
            items.forEach(item -> {
                serverDao.deleteByPrimaryKey(item.getId());
            });
            select();
        }
    }

    private void logWithSeparator(String separator, String message, final Object... objects) {
        final String formatStr = StringUtils.replace(message, "{}", "%s");
        Platform.runLater(() -> {
            txtLog.appendText(String.format(formatStr, objects) + separator);
        });
    }

    private void log(String message, final Object... objects) {
        logWithSeparator("\n", message, objects);
    }

    private String getResource(String name) {
        try {
            URL url = getClass().getClassLoader().getResource(name);
            if (url != null) {
                URI uri = url.toURI();
                if ("jar".equals(uri.getScheme())) {
                    FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
                    Path tempFilePath = Files.createTempFile("temp", getFileExtension(name));
                    Path filePathInJar = fileSystem.getPath("/" + name);
                    Files.copy(filePathInJar, tempFilePath, StandardCopyOption.REPLACE_EXISTING);
                    fileSystem.close();

                    // 在这里使用临时文件路径 tempFilePath 来访问数据库
                    return tempFilePath.toString();
                } else {
                    return uri.getPath();
                }
            }
            return getClass().getResource(name).getPath();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String getFileExtension(String filePath) {
        File file = new File(filePath);
        String fileName = file.getName();
        int dotIndex = fileName.lastIndexOf('.');

        if (dotIndex == -1 || dotIndex == fileName.length() - 1) {
            return "";
        } else {
            return fileName.substring(dotIndex + 1);
        }
    }

    public void sendCmd(ServerInfoItem server, List<String> cmdList) {
        final String key = server.getHost() + server.getPort();
        Session session = sessionPoolManager.getSession(key, server.getHost(), server.getPort(), server.getUsername(), server.getPassword());
        ChannelSftp channelSftp = null;
        try {
            // 打开SFTP通道
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();

            final String backupPattern = "yyyyMMdd_HHmmss";
            final String backupFileName = "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(backupPattern));
            final String sourceJar = server.getSourceJar();
            final String targetJar = server.getTargetJar();

            if (StringUtils.isBlank(server.getType()) || "后端".equals(server.getType())) {
                String sourceShell = getResource(DEPLOY_SHELL_NAME);
                final String targetShell = String.format("%s%s", server.getServerAppRoot(), DEPLOY_SHELL_NAME);
                if (cmdList.contains("00")) {
                    if (!FileUtil.exist(sourceJar)) {
                        log("服务器:{} 对应:{} 不存在", server.getName(), sourceJar);
                        return;
                    }
                }
                if (cmdList.contains("01")) {
                    // 先传递shell脚本
                    if (StringUtils.isNotBlank(targetShell)) {
                        log("服务器:{} 准备上传 {}", server.getName(), sourceShell);
                        // TODO&
                        toUnix(sourceShell);
                        channelSftp.put(sourceShell, targetShell, progressMonitor, ChannelSftp.OVERWRITE);
                    }
                }
                final String targetTmpDir = server.getServerAppRoot() + TEMP_NAME + "/" + targetJar;
                if (cmdList.contains("04")) {
                    final String createDirCommand = String.format("cd %s; [ -d %s ] || mkdir %s", server.getServerAppRoot(), TEMP_NAME, TEMP_NAME);
                    execute(session, "exec", createDirCommand, line -> log("创建临时目录服务器返回:{}", line), errLine -> log("创建临时目录服务器返回错误:{}", errLine));
                    // 执行文件上传
                    log("服务器:{} 本地文件 {} 上传到待发布临时地址 {}", server.getName(), sourceJar, targetTmpDir);
                    channelSftp.put(sourceJar, targetTmpDir, progressMonitor, ChannelSftp.OVERWRITE);
                    TimeUnit.SECONDS.sleep(1);
                }
                if (cmdList.contains("03")) {
                    String stopCommand = String.format("sh %s stop %s %s", targetShell, server.getAppPort(), server.getTargetJar());
                    // 先停止远程服务
                    execute(session, "exec", stopCommand, line -> log("服务器返回:{}", line), errLine -> log("服务器返回错误:{}", errLine));
                    TimeUnit.SECONDS.sleep(1);
                }
                if (cmdList.contains("02")) {
                    final String createDirCommand = String.format("cd %s; [ -d %s ] || mkdir %s", server.getServerAppRoot(), BACKUP_NAME, BACKUP_NAME);
                    execute(session, "exec", createDirCommand, line -> log("服务器返回:{}", line), errLine -> log("服务器返回错误:{}", errLine));

                    // 备份远程文件
                    final String backupName = BACKUP_NAME + "/" + targetJar + backupFileName;
                    execute(session, String.format("cd %s; cp %s %s", server.getServerAppRoot(), targetJar, backupName));
                    log("服务器:{} 备份 {} 到 {}", server.getName(), targetJar, backupName);
                    TimeUnit.SECONDS.sleep(1);
                }
                if (cmdList.contains("04")) {
                    execute(session, String.format("cp %s %s", targetTmpDir, server.getServerAppRoot() + targetJar));
                    log("将最新文件 {} 复制到 {}", targetTmpDir, server.getServerAppRoot() + targetJar);
                    TimeUnit.SECONDS.sleep(1);
                    final String delDir = server.getServerAppRoot() + TEMP_NAME;
                    execute(session, "exec", "rm -rf " + delDir, line -> log("服务器返回:{}", line), errLine -> log("服务器返回错误:{}", errLine));
                    log("删除临时文件目录 {}", delDir);
                }
                if (cmdList.contains("05")) {
                    log("服务器:{} 开始启动{}", server.getName(), targetJar);
                    // source ~/.bash_profile,/etc/profile
                    final String startCommand = String
                        .format("cd %s; source /etc/profile && nohup bash %s start %s %s %s", server.getServerAppRoot(), targetShell, server.getAppPort(), server.getTargetJar(),
                            StringUtils.defaultIfBlank(server.getStartExt(), ""));
                    execute(session, "exec", startCommand, line -> log("服务器返回: " + line), errLine -> log("服务端错误输出: " + errLine));
                    int maxSeconds = 60;
                    for (; ; ) {
                        maxSeconds -= 1;
                        String statusCommand = String.format("sh %s status %s %s", targetShell, server.getAppPort(), server.getTargetJar());
                        AtomicInteger status = new AtomicInteger();
                        execute(session, "exec", statusCommand, line -> {
                            log.info("服务端输出: " + line);
                            if (StringUtils.contains(line, "is running...")) {
                                status.set(1);
                            }
                            if (StringUtils.contains(line, "is running success")) {
                                status.set(2);
                            }
                        }, errLine -> log("服务端错误输出: " + errLine));
                        if (status.get() == 1) {
                            logWithSeparator("", " . . .");
                        }
                        if (status.get() == 2) {
                            log("服务器:{} 服务 {} 部署成功!", server.getName(), targetJar);
                            break;
                        }
                        TimeUnit.SECONDS.sleep(5);
                        if (maxSeconds <= 0) {
                            break;
                        }
                    }
                }
            }

            if ("前端".equals(server.getType())) {
                if (StringUtils.isNotBlank(sourceJar)) {
                    if (!FileUtil.exist(sourceJar)) {
                        log("前端服务器:{} 部署本地代码 {} 不存在", server.getName(), sourceJar);
                    } else {
                        // 前端根节点和后端略有不同
                        // 备份远程文件
                        final String backupName = BACKUP_NAME + "/" + targetJar + backupFileName;
                        final String backupFolderPath = server.getServerAppRoot() + backupName;
                        final String createFolderCommand = String.format("mkdir -p %s", backupFolderPath);
                        execute(session, "exec", createFolderCommand, line -> log("前端服务器返回:{}", line), errLine -> log("前端服务器返回错误:{}", errLine));
                        log("前端服务器:{} 创建备份目录 {}", server.getName(), backupFolderPath);
                        TimeUnit.SECONDS.sleep(1);

                        final String backupCommand = String.format("cd %s; cp -r %s %s", server.getServerAppRoot(), targetJar, backupName);
                        execute(session, "exec", backupCommand, line -> log("前端服务器:{}", line), errLine -> log("前端服务器返回错误:{}", errLine));
                        log("前端服务器:{} 备份文件到 {} ", server.getName(), backupName);
                        TimeUnit.SECONDS.sleep(1);
                        final String remotePath = server.getServerAppRoot() + targetJar;
                        execute(session, "rm -rf " + remotePath);
                        // 执行文件上传
                        uploadDirectory(channelSftp, progressMonitor, sourceJar, remotePath);
                    }
                }
            }

            log("done");
        } catch (Exception e) {
            log("发生异常: {}", e.getMessage());
        } finally {
            // 关闭SFTP通道和SSH会话
            if (channelSftp != null) {
                channelSftp.disconnect();
            }
            sessionPoolManager.releaseSession(key, session);
        }
    }

    private SftpProgressMonitor progressMonitor = new SftpProgressMonitor() {
        String src;
        long transferred;
        long fileSize;

        @Override
        public void init(int op, String src, String dest, long max) {
            if (op == SftpProgressMonitor.PUT) {
                fileSize = max;
            } else {
                fileSize = max;
            }
            this.src = src;
            transferred = 0;
        }

        @Override
        public boolean count(long count) {
            transferred += count;
            double progress = calculateProgress(fileSize, transferred);

            StringBuilder progressBar = new StringBuilder("上传" + src + "进度: [");
            int totalBars = 50;
            int filledBars = (int) (progress / 2);
            for (int i = 0; i < filledBars; i++) {
                progressBar.append("=");
            }
            for (int i = filledBars; i < totalBars; i++) {
                progressBar.append(" ");
            }
            progressBar.append("] " + progress + "%");

            Platform.runLater(() -> {
                String currentLog = txtLog.getText(); // 获取当前日志文本

                String newProgressBar = progressBar.toString(); // 新的进度条文本

                String name = "上传" + src + "进度:";
                // 检查日志中是否已包含进度条文本
                if (currentLog.contains(name)) {
                    // 替换已有的进度条文本
                    String newLog;
                    String escapedName = name;
                    String os = System.getProperty("os.name").toLowerCase();
                    if (os.contains("windows")) {
                        escapedName = Pattern.quote(name);
                        newProgressBar = newProgressBar.replaceAll("[\\\\.*\n]", "\\\\$0");
                        newLog = currentLog.replaceAll(escapedName + ".*\n", newProgressBar + "\n");
                    } else {
                        newLog = currentLog.replaceAll(escapedName + ".*\n", newProgressBar + "\n");
                    }
                    txtLog.setText(newLog); // 更新日志文本
                } else {
                    // 添加新的进度条文本
                    txtLog.appendText(newProgressBar + "\n");
                }
            });

            return true;
        }

        @Override
        public void end() {

        }

        double calculateProgress(long totalTasks, long completedTasks) {
            double progress = (double) completedTasks / totalTasks * 100;
            return progress;
        }
    };

    private void toUnix(String source) {
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains("windows")) {
            source = convertToWindowsPath(source);
            log("当前操作系统是{},需要将{}转换为Unix格式", os, source);
            try {
                File file = new File(source);
                BufferedReader reader = new BufferedReader(new FileReader(file));
                StringBuilder content = new StringBuilder();

                String line;
                while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                reader.close();

                FileWriter writer = new FileWriter(file);
                writer.write(content.toString());
                writer.close();
                log("转换完成");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static String convertToWindowsPath(String unixPath) {
        File file = new File(unixPath);
        return file.getAbsolutePath();
    }

    private static void uploadDirectory(ChannelSftp sftpChannel, SftpProgressMonitor progressMonitor, String localPath, String remotePath) throws SftpException {
        File localFile = new File(localPath);
        if (localFile.isDirectory()) {
            try {
                sftpChannel.cd(remotePath);
            } catch (SftpException e) {
                // 目录不存在，创建目录
                sftpChannel.mkdir(remotePath);
                sftpChannel.cd(remotePath);
            }

            String[] files = localFile.list();
            if (files != null) {
                for (String fileName : files) {
                    String localFilePath = localPath + "/" + fileName;
                    String remoteFilePath = remotePath + "/" + fileName;
                    uploadDirectory(sftpChannel, progressMonitor, localFilePath, remoteFilePath);
                }
            }
        } else {
            sftpChannel.put(localPath, remotePath, progressMonitor, ChannelSftp.OVERWRITE);
        }
    }

    private static void execute(Session session, String command) {
        execute(session, "exec", command, l -> {
        }, el -> {
        });
    }

    private static void execute(Session session, String type, String command, Consumer<String> suc, Consumer<String> err) {
        ChannelExec channelExec = null;
        BufferedReader reader = null;
        BufferedReader errReader = null;
        String line, errLine;
        try {
            channelExec = (ChannelExec) session.openChannel(type);
            channelExec.setCommand(command);
            channelExec.setInputStream(null); // 禁用输入流
            channelExec.setPty(true);
            channelExec.connect();
            reader = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));
            errReader = new BufferedReader(new InputStreamReader(channelExec.getErrStream()));

            while ((line = reader.readLine()) != null) {
                suc.accept(line);
            }

            while ((errLine = errReader.readLine()) != null) {
                err.accept(errLine);
            }
        } catch (Exception e) {
            log.error("", e);
        } finally {
            try {
                if (null != reader) {
                    reader.close();
                }
                if (null != errReader) {
                    errReader.close();
                }
                if (null != channelExec) {
                    channelExec.disconnect();
                }
            } catch (Exception e) {
                log.error("", e);
            }
        }

    }

}
